/** @file    TelnetSession.cpp
 *  @author  Alessandro Polo
 *  @version $Id: TelnetSession.cpp 2880 2010-08-09 03:17:19Z alex $
 *  @brief
 * File containing methods for the wosh::TelnetSession class.
 * The header for this class can be found in TelnetSession.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "TelnetSession.h"
 
 #include <core/ObjectFactory.h>
 #include <core/Utilities.h>
 #include <core/WoshKernel.h>
 #include <core/Request.h>
 #include <core/DataModels.h>
 #include <framework/security/SecurityAuthenticationShell.h>

 #define _ENDLINE	"\r\n"


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

TelnetSession::TelnetSession()
	: wosh::ThreadImpl(NULL, "wosh.TelnetSession"), wosh::Session() {
	Object::setName("TelnetSession", getID());
	Log.setContext( Object::getName(), true );

	this->listener = NULL;
	this->socket_id = 0;
	this->tcpSocket = NULL;
	this->writing = false;

Log.setLevel( LOG_DEBUG);

	this->msgBufferIn = new char[_TelnetSession_BUFFER];
	memset( this->msgBufferIn, '\0', _TelnetSession_BUFFER );
	this->msgBufferOut = new char[_TelnetSession_BUFFER];
	memset( this->msgBufferOut, '\0', _TelnetSession_BUFFER );
	this->msgBufferOutSize = 0;

	this->credentials = new wosh::security::SecurityTokenProviderShell();

	setWelcomeMessage("Welcome WOSH TelnetSession v." _ENDLINE );
 }

TelnetSession::~TelnetSession() {
	Log(LOG_VERBOSE, ":~TelnetSession() Destroying.." );
	if ( isThreadRunning() )
		quitThread(60000);
	Log(LOG_DEBUG, ":~TelnetSession() Closing buffers.." );
	this->msgBufferOutMux.lockForWrite();
	if ( this->msgBufferOut != NULL )
		delete [] this->msgBufferOut;
	this->msgBufferOut = NULL;
	this->msgBufferOutSize = 0;
	this->msgBufferOutMux.unLock();
	if ( this->msgBufferIn != NULL )
		delete [] this->msgBufferIn;
	this->msgBufferIn = NULL;

	delete this->credentials; this->credentials =NULL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// SESSION CONTROL

WRESULT TelnetSession::open( unsigned long timeout ) {
	if ( this->protocol == NULL ) return WRET_ERR_INTERNAL;
	return startThread(timeout);
 }

WRESULT TelnetSession::close( unsigned long timeout ) {
	return quitThread(timeout);
 }

///////////////////////////////////////////////////////////////////////////////////////////// SESSION CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool TelnetSession::initializingThread() {
	if ( this->socket_id == 0  ) {
		Log(LOG_CRITICAL, ":initializingThread() Invalid SocketDescriptor.." );
		return false;
	 }
	this->tcpSocket = ObjectFactory::createTypeOf<wosh::network::SocketTcp>(false);
	if ( this->tcpSocket == NULL ) {
		Log(LOG_CRITICAL, ":initializingThread() FAILED Creating SocketTcp!" );
		return false;
	 }
	Log(LOG_VERBOSE, ":initializingThread() : Acquiring SocketID[%d]..", this->socket_id );
	this->tcpSocket->setListener(this);
	this->tcpSocket->setSocket(this->socket_id);
	QObject::connect(this, SIGNAL(writeData_s()), this, SLOT(writeData()), Qt::QueuedConnection );
	return true;
 }

void TelnetSession::runThread() {
	Log(LOG_VERBOSE, ":runThread() Logging in.." );

	if ( this->listener != NULL )
		this->listener->session_connected(this);

	setState(Session::STATE_LOGGING);
	this->tcpSocket->sendDatagram( _ENDLINE );
	int retries = 0;
	while( this->running ) {
		if ( ++retries >= getMaxLoginRetries() ) {
			Log(LOG_WARNING, ":runThread() Exiting due to max-retries limit" );
			return;
		 }
		if ( handleLogin(this->running) )
			break;
		if ( isFailed() ) {
			Log(LOG_CRITICAL, ":runThread() FAILURE state! Exiting loop" );
			break;
		 }
	 }
	Log(LOG_VERBOSE, ":runThread() Handling Session.." );
	WRESULT opened = Session::open(); // base implementation!!!
	if ( WFAILED(opened) ) {
		Log(LOG_CRITICAL, ":runThread() FAILED opening session" );
		return;
	 }
	if ( isWelcomePrompted() )
		this->tcpSocket->sendDatagram( getWelcomeMessage() + _ENDLINE );

	Log(LOG_DEBUG, ":runThread() Entering QT Exec" );
	this->tcpSocket->sendDatagram( _ENDLINE + getPrompt() );
	exec();
	this->tcpSocket->sendDatagram( _ENDLINE + std::string("Session Closed.") + _ENDLINE);

	WRESULT closed = Session::close(); // base implementation!!!
	if ( WFAILED(closed) ) {
		Log(LOG_CRITICAL, ":handleSession() FAILED closing session" );
	 }

 }

void TelnetSession::exitingThread() {

	if ( this->tcpSocket->isConnected() ) {
		WRESULT ret = this->tcpSocket->disconnect();
		if ( WFAILED(ret) )
			Log(LOG_WARNING, ":exitingThread() FAILED#%d Disconnecting Socket..", ret );
	 }
	Log(LOG_VERBOSE, ":exitingThread() Destroying TCP Socket.." );
	WRESULT destroyed = ObjectFactory::destroy(this->tcpSocket);
	if ( WFAILED(destroyed) ) {
		Log(LOG_CRITICAL, ":exitingThread() FAILED#%d Destroying Socket", destroyed );
	 }
	this->tcpSocket = NULL;

	if ( this->listener != NULL )
		this->listener->session_disconnected(this);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool TelnetSession::handleLogin( bool& still_running ) {
	Log(LOG_VERBOSE, ":handleLogin() Logging In.." );
	setState(Session::STATE_LOGGING);
	// flush socket
	this->tcpSocket->waitData(2000);
	this->tcpSocket->read(this->msgBufferIn, _TelnetSession_BUFFER );
	this->logging_user = "";
	this->logging_pwd = "";
	while ( this->logging_user.size() < 3 ) {
		setState(Session::STATE_LOGGING_USER);
		this->tcpSocket->sendDatagram("Username: ");
		if ( !this->tcpSocket->waitData(30000) )
			continue;
		if ( this->logging_user.empty() ) continue;
		if ( !still_running ) return false;
	 }
	setState(Session::STATE_LOGGING_PWD);
	this->tcpSocket->sendDatagram("Password: ");
	if ( !this->tcpSocket->waitData(30000) )
		return false;
	if ( this->logging_pwd.empty() ) {
		Log(LOG_DEBUG, ":handleLogin() Invalid empty password!" );
		return false;
	 }
	if ( !still_running ) return false;
	Log(LOG_VERBOSE, ":handleLogin() Logging In user '%s',pwd[%d]..", this->logging_user.c_str(), this->logging_pwd.size() );
	setUsernameLogged(this->logging_pwd);
	WRESULT ret = login();
	if ( WFAILED(ret) ) {
		return false;
	 }
	return true;
 }

WRESULT TelnetSession::printMessage( const std::string& message ) {
	if ( !this->running ) return WRET_ERR_ILLEGAL_USE;
	if ( this->tcpSocket == NULL || this->msgBufferOut == NULL ) return WRET_ERR_INTERNAL;

	int i = (10*5) * 20;//sec
	while( this->writing && --i > 0 )
		ThreadImpl::sleepForMSec(20);
	if ( this->writing ) {
		Log(LOG_CRITICAL, ":sendMessage() STILL writing [timeout]!? [FORCED RESET]" );
		this->writing = false;
		return WRET_ERR_INTERNAL;
	 }
	std::string prompt = getPrompt();
	this->msgBufferOutMux.lockForWrite();
	this->msgBufferOutSize = message.size()+2+prompt.size();
	int overflow = (_TelnetSession_BUFFER-1) - this->msgBufferOutSize;
	if ( overflow < 0 ) { // lame
		Log(LOG_CRITICAL, ":sendMessage() Buffer Overflow! [%ld < %ld]", (_TelnetSession_BUFFER-1), this->msgBufferOutSize );
		return WRET_ERR_INTERNAL;
	 }
	strncpy(this->msgBufferOut, message.c_str(), message.size() );
	strncpy(this->msgBufferOut+message.size(), _ENDLINE, 2 );
	strncpy(this->msgBufferOut+message.size()+2, prompt.c_str(), prompt.size() );
	this->writing = true;
	this->msgBufferOutMux.unLock();

	emit writeData_s();
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void TelnetSession::writeData() {
	this->msgBufferOutMux.lockForRead();
	int size = this->msgBufferOutSize;
	if ( size == 0 ) {
		this->msgBufferOutMux.unLock();
		return;
	 }
	this->writing = true;

//	this->tcpSocket->sendDatagram((char*)&size, sizeof(long));
	int bs = this->tcpSocket->sendDatagram(this->msgBufferOut, size);
	this->writing = false;
	this->msgBufferOutSize = 0;
	this->msgBufferOutMux.unLock();
	if ( bs != size ) {
		Log(LOG_WARNING, ":writeData() FAILED Sending data [size: %d, sent: %d]", size, bs );
		return;
	 }
	else
		Log(LOG_VERBOSE, ":writeData() sent data [%d bytes]", size );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void TelnetSession::tcpDataAvailable( long data_len, wosh::network::SocketTcp* socket ) {
	if ( socket == NULL ) return;
	if ( Log.isDebug() )
		Log(LOG_DEBUG, ":tcpDataAvailable(%ld) '%s'", data_len, this->msgBufferIn );
	else
		Log(LOG_VERBOSE, ":tcpDataAvailable(%ld) ..", data_len );
	data_len = _GET_Min(data_len, _TelnetSession_BUFFER);
	data_len = socket->read(this->msgBufferIn, data_len );
	if ( data_len <= 2 ) { // newline = 2 chars
		this->tcpSocket->sendDatagram(getPrompt());
		return;
	 }
	std::string buffer;
	buffer.assign(this->msgBufferIn, data_len-2);

	if ( (getState() & (Session::STATE_LOGGING)) == Session::STATE_LOGGING ) {
		if ( getState() == Session::STATE_LOGGING_USER ) // getting username
			this->logging_user = buffer;
		else if ( getState() == Session::STATE_LOGGING_PWD ) // getting password
			this->logging_pwd = buffer;
		return;
	 }
	if ( getState() != Session::STATE_OPENED ) {
		Log(LOG_WARNING, ":tcpDataAvailable(%ld) : Ignored because session is not opened!", data_len );
		return;
	 }
	if ( !buffer.empty() )
		parseInput(buffer);

	setLastInputNow();
	this->tcpSocket->sendDatagram(getPrompt());
 }

void TelnetSession::tcpSocketError( int error_code, const char* description, wosh::network::SocketTcp* ) {
	if ( description == NULL )
		Log(LOG_WARNING, ":tcpSocketError(%d) Unknown Error description", error_code );
	else
		Log(LOG_WARNING, ":tcpSocketError(%d) : %s", error_code, description );
	switch(error_code) {
		case _SocketTcp_ERROR_RemoteHostClosedError: {
			break;
		 }
		default: {
			break;
		 }
	 }
	if ( isThreadRunning() ) {
		if ( getState() == Session::STATE_LOGGING )
			return;
		Log(LOG_VERBOSE, ":tcpSocketError() : Thread is Running, Exiting.." );
		ThreadImpl::quitThread(0); // ASYNCH!
	 }
	else {
		if ( this->listener != NULL )
			this->listener->session_error(this);
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void TelnetSession::registered() {
	if ( this->listener == NULL ) return;
	this->listener->session_registration(1,this);
 }

void TelnetSession::unregistered() {
	if ( this->listener == NULL ) return;
	this->listener->session_registration(0,this);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool TelnetSession::isConnected() const {
	return ( this->tcpSocket != NULL && this->tcpSocket->isConnected() );
 }

DataFieldCollector<TelnetSession>* TelnetSession::getDataFields() {
	DataFieldCollector<TelnetSession>* fields = new DataFieldCollector<TelnetSession>();
	fields->add<long, Variant>(&Session::getID, "ID", 0);
	fields->add<const std::string&, Variant>(&Session::getName, "Name", 1);
	fields->add<const char*, Variant>(&Session::getClassName, "Type", 2);
	fields->add<const char*,Variant>(&Session::getStateAsString, "State", 3);
	fields->add<const std::string&,Variant>(&Session::getUsername, "Username", 4);
	fields->add<long,Variant_DT>(&Session::getLastInputTS, "LastInputTS", 5);
	fields->add<const std::string&,Variant>(&Session::getEntityID, "EntityID", 7, false);
	fields->add<const URI&,URI>(&Session::getURI, "URI", 8, false);
	fields->add<const Permission&,Permission>(&Session::getPermission, "Permission", 9, false);
	return fields;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
